
#include <stdlib.h>
#include <stdio.h>
#include <png.h>
#include "utility.h"
#include "qr-common.h"

int write_png_for_qrcode(int version, unsigned char* data, const char* filename)
{
	static FILE* fp;	// Avoid clobbering by setjmp.
	png_structp png_ptr;
	png_infop info_ptr;
	int width, png_width, row_length, margin_size, bit, x, y, i;
	unsigned char* row, * ptr;

	if(qr_check_version(version) < 0 || NULL == data || NULL == filename)
	{
		RES_INVAL("Argument is invalid.");
	}
	if(strlen(filename) <= 0)
	{
		RES_INVAL("Argument is invalid.");
	}

	width = qr_width(version);
	if(width <= 0)
	{
		RES_INVAL("Invoke qr_width error.");
	}
	png_width = (width + (PNG_MARGIN << 1)) * PNG_SIZE;
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if(NULL == png_ptr)
	{
		RES_INVAL("Invoke png_create_write_struct error.");
	}
	info_ptr = png_create_info_struct(png_ptr);
	if(NULL == info_ptr)
	{
		png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
		RES_INVAL("Invoke png_create_info_struct error.");
	}
	if(setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_write_struct(&png_ptr, &info_ptr);
		RES_INVAL("Invoke setjmp error.");
	}
	fp = fopen(filename, "wb");
	if(NULL == fp)
	{
		png_destroy_write_struct(&png_ptr, &info_ptr);
		RES_INVAL("Invoke fopen error.");
	}
	png_init_io(png_ptr, fp);
	png_set_IHDR(png_ptr, info_ptr, png_width, png_width, 1, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	png_write_info(png_ptr, info_ptr);
	row_length = ((png_width + 7) >> 3) * sizeof(unsigned char);
	row = (unsigned char*)malloc(row_length);
	if(NULL == row)
	{
		png_destroy_write_struct(&png_ptr, &info_ptr);
		fclose(fp);
		RES_INVAL("Invoke malloc error.");
	}
	margin_size = PNG_MARGIN * PNG_SIZE;

	// Write top margin.
	memset(row, 0xff, row_length);
	for(y = 0; y < margin_size; ++y)
	{
		png_write_row(png_ptr, row);
	}
	// Write data.
	for(y = 0; y < width; ++y)
	{
		memset(row, 0xff, row_length);
		ptr = row + (margin_size >> 3);
		bit = 7 - (margin_size % 8);
		for(x = 0; x < width; ++x)
		{
			for(i = 0; i < PNG_SIZE; ++i)
			{
				*ptr ^= (*data & 1) << bit;
				--bit;
				if(bit < 0)
				{
					++ptr;
					bit = 7;
				}
			}
			++data;
		}
		for(i = 0; i < PNG_SIZE; ++i)
		{
			png_write_row(png_ptr, row);
		}
	}
	// Write bottom margin.
	memset(row, 0xff, row_length);
	for(y = 0; y < margin_size; ++y)
	{
		png_write_row(png_ptr, row);
	}

	free(row);
	png_write_end(png_ptr, info_ptr);
	png_destroy_write_struct(&png_ptr, &info_ptr);
	fclose(fp);
	return 0;
}

